package org.suburban.controller;
import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withUrl;
import static org.suburban.resource.JIDs.normalise;
import static org.suburban.resource.RequestParams.*;
import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.xmpp.Subscription;
import com.google.appengine.api.xmpp.Presence;
import com.google.appengine.api.xmpp.Message;
import com.google.appengine.api.xmpp.JID;
import org.suburban.controller.AdminCommandTranslator.AdminCommand;
import org.suburban.controller.AdminCommandTranslator.AdminCommandArguments;
import org.suburban.controller.ClientCommandTranslator.ClientCommand;
import org.suburban.data.Station;
import org.suburban.data.StationRepository;
import org.suburban.resource.JIDs;
import org.suburban.resource.Queues;
import org.suburban.resource.URLs;
import java.util.logging.Logger;
import static java.util.logging.Level.*;

/**
 * Class to implement main controller logic
 * @author Alexey Palamarchuk (slonnik@gmail.com)
 */
public final class Controller {

	/** static controller instance*/
	private static final Controller instance = new Controller();
	/** logger*/
	private static final Logger logger = Logger.getLogger(Controller.class.getName());
	
	
	/**
	 * ctor
	 */
	private Controller()
	{
		
	}
	
	/**
	 * Factory method
	 * @return controller instance
	 */
	public static Controller getInstance()
	{
		return instance;
	}
	
	/**
	 * Method to handle subscription request
	 * @param subscription request
	 */
	public void handleSubscription(Subscription subscription)
	{
		/** check if subscription request is made under 'admin' mode*/
		if(JIDs.isAdminJid(subscription.getToJid()) == true)
		{
			handleAdminSubscription(subscription);
		}
		else
		{
			handleClientSubscription(subscription);
		}
	}
	
	/**
	 * Method to handle subscription request, made in 'admin' mode
	 * @param subscription
	 */
	private void handleAdminSubscription(Subscription subscription)
	{
		Queue queue = QueueFactory.getQueue(Queues.UTILITY);
		queue.add(withUrl(URLs.ADMIN_WELCOME)
				.param(ORIGINATOR, normalise(subscription.getFromJid()).getId()));
				
	}
	
	/**
	 * Method to handle subscription request, made in 'client' mode 
	 * @param subscription
	 */
	private void handleClientSubscription(Subscription subscription)
	{
		/** subscription originator*/
		JID originator = normalise(subscription.getFromJid());
		/** subscription client*/
		JID client = normalise(subscription.getToJid());
		
		/** get schedule station ids*/
		String[] stationIDs = getStationIDs(client);
		String scheduleName = getScheduleNameFromIDs(stationIDs);
		
		/** send 'welcome' request*/
		Queue queue = QueueFactory.getQueue(Queues.UTILITY);
		queue.add(withUrl(URLs.CLIENT_WELCOME)
				.param(ORIGINATOR, originator.getId())
				.param(CLIENT, normalise(subscription.getToJid()).getId())
				.param(SCHEDULE_NAME, scheduleName));
	}
	
	/**
	 * Method to handle presence request
	 * @param presence request
	 */
	public void handlePresence(Presence presence)
	{
		Queue queue = QueueFactory.getQueue(Queues.UTILITY);
		queue.add(withUrl(URLs.PRESENCE)
				.param(ORIGINATOR, normalise(presence.getFromJid()).getId())
				.param(CLIENT, normalise(presence.getToJid()).getId()));
	}
	
	/**
	 * Method to handle chat
	 * @param message
	 */
	public void handleChat(Message message)
	{
		/** check if incoming 'chat' message is under 'admin' mode*/
		if(JIDs.isAdminJid(message.getRecipientJids()[0]) == true)
		{
			handleAdminChat(message);
		}
		else
		{
			handleClientChat(message);
		}
	}
	
	/**
	 * Method to handle 'chat' under 'admin' mode
	 * @param message - chat message
	 */
	private void handleAdminChat(Message message)
	{
		/** originator JID*/
		JID originator = JIDs.normalise(message.getFromJid());
		/** command body*/
		String body = message.getBody();
		
		/** we don't process empty messages */
		if((body == null) || (body.trim().length() == 0))
		{
			return;
		}
		/** translate command*/
		AdminCommand command =AdminCommandTranslator.translate(body.trim());
		switch(command.type())
		{
			case SCHEDULE:
			{
				/** departure name*/
				String departure = command.argument(AdminCommandArguments.DEPATURE);
				/** arrival name*/
				String arrival = command.argument(AdminCommandArguments.ARRIVAL);
					
				logger.log(FINE, "Try to to create schedule for {0} - {1}", new Object[]{departure, arrival});
				/** get possible departure stations for particular departure name*/
				Station[] depStations = StationRepository.getStationByName(departure);
				/** get possible arrival stations for particular arrival name*/
				Station[] arrStations = StationRepository.getStationByName(arrival);
				
				/** trace all departure/arrival stations we found*/
				for(Station depStation : depStations)
				{
					logger.log(FINE, "Database departure: {0}", depStation);
				}
				
				for(Station arrStation : arrStations)
				{
					logger.log(FINE, "Database arival: {0}", arrStation);
				}
				
				/** try to locate departure and arrival on the same direction*/
				for(Station depStation : depStations)
				{
					for(Station arrStation : arrStations)
					{
						int compare = depStation.getDirectionId().compareTo(arrStation.getDirectionId());
						if(compare == 0)
						{
							/** create new client JID*/
							JID client = JIDs.newClientJid(String.format("%1$s-%2$s", 
									depStation.getId(),
									arrStation.getId()));
							
							/** get schedule name*/
							String scheduleName = getScheduleNameFromIDs(depStation.getId(), arrStation.getId());
							
							/** finally, we found departure and arrival stations, send invitation*/
							Queue queue = QueueFactory.getQueue(Queues.UTILITY);
							queue.add(withUrl(URLs.CLIENT_INVITE)
									.param(SCHEDULE_NAME, scheduleName)
									.param(ORIGINATOR, originator.getId())
									.param(CLIENT, client.getId()));
						}
						
					}
				}
				logger.log(WARNING, "Failed to create schedule for departure='{0}' and arrival='{1}'", 
						new String[] {departure, arrival});
				break;
			}
			case HELP:
			{
				/** send 'admin' help message to the client*/
				Queue queue = QueueFactory.getQueue(Queues.UTILITY);
				queue.add(withUrl(URLs.ADMIN_HELP)
						.param(ORIGINATOR, originator.getId()));
				break;
			}
			case ERROR:
			{
				logger.log(WARNING, "'handleAdminChat' error");
				break;
			}
		}
	}
	
	/**
	 * Method to handle 'chat' under 'client' mode
	 * @param message - chat message
	 */
	private void handleClientChat(Message message)
	{
		/** originator JID*/
		JID originator = JIDs.normalise(message.getFromJid());
		/** client JID*/
		JID client = JIDs.normalise(message.getRecipientJids()[0]);
		/** command body*/
		String body = message.getBody();
		
		/** we don't process empty messages */
		if((body == null) || (body.trim().length() == 0))
		{
			return;
		}
		/** translate incoming message*/
		ClientCommand command = ClientCommandTranslator.translate(body.trim());
		switch(command.type())
		{
			case UPTOTIME_SCHEDULE:
			{
				/** get schedule station ids*/
				String[] stationIDs = getStationIDs(client);
				/** get direction*/
				String direction = StationRepository.getDirection(stationIDs[0]);
				/** get schedule human readable name*/
				String scheduleName = getScheduleNameFromIDs(stationIDs);
				
				Queue queue = QueueFactory.getQueue(Queues.UP_TO_TIME);
				queue.add(withUrl(URLs.UP_TO_TIME)
						.param(ORIGINATOR, originator.getId())
						.param(CLIENT, client.getId())
						.param(DEPARTURE, stationIDs[0])
						.param(ARRIVAL, stationIDs[1])
						.param(DIRECTION, direction)
						.param(SCHEDULE_NAME, scheduleName));
				break;
			}
			case UPTOTIME_SCHEDULE_REVERSE:
			{
				/** get schedule station ids*/
				String[] stationIDs = getStationIDs(client);
				/** get direction*/
				String direction = StationRepository.getDirection(stationIDs[0]);
				/** get schedule human readable name*/
				String scheduleName = getScheduleNameFromIDs(stationIDs[1], stationIDs[0]);
				
				Queue queue = QueueFactory.getQueue(Queues.UP_TO_TIME);
				queue.add(withUrl(URLs.UP_TO_TIME)
						.param(ORIGINATOR, originator.getId())
						.param(CLIENT, client.getId())
						.param(DEPARTURE, stationIDs[1])
						.param(ARRIVAL, stationIDs[0])
						.param(DIRECTION, direction)
						.param(SCHEDULE_NAME, scheduleName));
				break;
			}
			case FULL_SCHEDULE:
			{
				/** get schedule station ids*/
				String[] stationIDs = getStationIDs(client);
				/** get direction*/
				String direction = StationRepository.getDirection(stationIDs[0]);
				/** get schedule human readable name*/
				String scheduleName = getScheduleNameFromIDs(stationIDs);
				
				Queue queue = QueueFactory.getQueue(Queues.UP_TO_TIME);
				queue.add(withUrl(URLs.FULL_DAY)
						.param(ORIGINATOR, originator.getId())
						.param(CLIENT, client.getId())
						.param(DEPARTURE, stationIDs[0])
						.param(ARRIVAL, stationIDs[1])
						.param(DIRECTION, direction)
						.param(IS_EMAIL, "true")
						.param(IS_XML, "true")
						.param(SCHEDULE_NAME, scheduleName));
				break;
			}
			case FULL_SCHEDULE_REVERSE:
			{
				/** get schedule station ids*/
				String[] stationIDs = getStationIDs(client);
				/** get direction*/
				String direction = StationRepository.getDirection(stationIDs[0]);
				/** get schedule human readable name*/
				String scheduleName = getScheduleNameFromIDs(stationIDs[1], stationIDs[0]);
				
				Queue queue = QueueFactory.getQueue(Queues.UP_TO_TIME);
				queue.add(withUrl(URLs.FULL_DAY)
						.param(ORIGINATOR, originator.getId())
						.param(CLIENT, client.getId())
						.param(DEPARTURE, stationIDs[1])
						.param(ARRIVAL, stationIDs[0])
						.param(DIRECTION, direction)
						.param(IS_EMAIL, "true")
						.param(IS_XML, "true")
						.param(SCHEDULE_NAME, scheduleName));
				break;
			}
			case HELP:
			{
				/** send 'client' help message to the client*/
				Queue queue = QueueFactory.getQueue(Queues.UTILITY);
				queue.add(withUrl(URLs.CLIENT_HELP)
						.param(ORIGINATOR, originator.getId())
						.param(CLIENT, client.getId()));
				break;
			}
			case ERROR:
			{
				logger.log(WARNING, "'handleClientChat' error");
				break;
			}
		}
		
	}
	
	
	/**
	 * Utility method to get stations ids from JID
	 * @param jid - client JID 
	 * @return - stations ids
	 */
	private String[] getStationIDs(JID jid)
	{
		/** client has following format: <departure>-<arrival>@domain*/
		return jid.getId().split("@")[0].split("-"); //here data[0] - departure; data[1] -arrival
	}
	/**
	 * Utility method to get schedule name from station ids
	 * @param jid - client JID 
	 * @return - departure + arrival human readable names
	 */
	private String getScheduleNameFromIDs(String... ids)
	{
		/** get human readable name for every station id*/
		return  String.format("%1$s-%2$s", StationRepository.getName(ids[0]), StationRepository.getName(ids[1]));
	}
}
